#include <unistd.h>
#include <sys/types.h>
#include <getopt.h>

#include "common.h"
#include "debuglog.h"
#include "socketlib.h"

#define VERSION "v0.0.1"
#define MAX_MSG_LENGTH 1504

FILE *g_logstream = NULL;
int g_debuglevel = LOG_INFO;
char *g_progname = NULL;

struct option long_option[] = {
    {"help",      0,  NULL, 'h'},
    {"port",      1,  NULL, 'p'},
    {"first",     1,  NULL, 'f'},
    {"second",    1,  NULL, 's'},
    {"grpaddr",   1,  NULL, 'g'},
    {"debug",     1,  NULL, 'd'},
    {"version",   0,  NULL, 'v'},

    {NULL, 0, NULL, 0},
};

/**
 *  @brief usage infomation tip 
 *  @arg progname program name
 *       status program input status
 *
 *  @return void
 */
static void usage(char *progname, int status)
{
    if (status != 0) 
    {
        fprintf (stderr, "Try `%s --help' for more information.\n", progname);
    } 
    else 
    {
        fprintf(stdout, "Usage: %s [OPTION]... \n"
               "multicast interface relay packet.\n\n"
               "-h  --help      help\n"
               "-f  --first     device interface\n"
               "-s  --second    transport port number\n"
               "-g  --grpaddr   group ip addr\n"
               "-d  --debug     debug level [0-all,1-debug,2-info,3-warning,4-error,5-fatal,6-off]\n"
               "-o  --out       log output file.\n"
               "-p  --port      forward port\n"
               "-v  --version   version infomation\n\n"
               "for help, please contact luomin5417@gmail.com",
               progname);
        fprintf(stdout, "\n");
    }

    exit(status);
}

/**
 *  @brief Print the program version infomation.
 *  @arg 
 *
 *  @return void
 */
static void print_version()
{
    #ifdef RIVISION
        fprintf(stdout, "%s\n", RIVISION);
    #else
        fprintf(stdout, "%s\n", VERSION);
    #endif

    exit(EXIT_SUCCESS);
}

/**
 *  @brief multiforward exit function.
 *  @arg 
 *
 *  @return void
 */
void template_exit()
{
    DEBUG_LOG(LOG_INFO, "%s program exit.\n", g_progname);

    if (g_logstream != NULL)
    {
        fclose(g_logstream);
    }
    exit(EXIT_SUCCESS);
}

/**
 *  @brief sigint proc handle
 *  @arg
 *
 *  @return void
 */
void sigint_proc_handler()
{
    DEBUG_LOG(LOG_INFO, "Receive ctrl+c signal.");
    template_exit();
}

/**
 *  @brief signal proc handler setup.
 *  @arg
 *
 *  @return void
 */
void setup_signal_handler()
{
    signal(SIGINT, sigint_proc_handler);
}

/**
 *  @brief The program main funciton.
 *  @arg argc the input arg counter;
 *       argv the input every args info.
 *
 *  @return int value
 */
int main(int argc, char *argv[])
{
    int8_t intf0[32] = {0};
    int8_t intf1[32] = {0};
    int32_t port;
    unsigned char *p = NULL;
    int32_t chvar = 0, len = 0;
    int32_t socketintf0, socketintf1;
    int32_t maxsock;
    fd_set selset;
    int32_t retval;
    int8_t *pbuf = (int8_t *)malloc(MAX_MSG_LENGTH);
    int8_t grpaddr[32] = {0};
    int32_t ret = 0;
    struct sockaddr_in intf0dstaddr;
    struct sockaddr_in intf1dstaddr;
    struct sockaddr_in intf0srcaddr;
    struct sockaddr_in intf1srcaddr;
    int32_t intf0_recv_count = 0, intf1_recv_count = 0;
    int32_t intf0_send_count = 0, intf1_send_count = 0;
    struct ifreq intf0req, intf1req;
    struct sockaddr_in *intf0ip, *intf1ip;
    int32_t socklen = sizeof(struct sockaddr);
    char logfile[256] = {0};

    /* Get program name. */
    g_progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]);
    while (true) 
    {
        if ((chvar = getopt_long(argc, argv, "f:s:p:g:d:o:vh", long_option, NULL)) < 0) 
        {
            break;
        }

        switch(chvar) 
        {
        case 'p':
            port = atoi(optarg);
            break;
        case 'd':
            g_debuglevel = atoi(optarg);
            break;
        case 'o':
            memcpy(logfile, optarg, sizeof(logfile));
            g_logstream = fopen((const char *)logfile, "a+");
            if (g_logstream == NULL)
            {
                DEBUG_LOG(LOG_FATAL, "open log file failed.");
                return ERROR;
            }
            break;
        case 'f':
            memcpy(intf0, optarg, sizeof(intf0));
            break;
        case 's':
            memcpy(intf1, optarg, sizeof(intf1));
            break;
        case 'g':
            memcpy(grpaddr, optarg, sizeof(grpaddr));
            break;
        case 'h':
            usage(g_progname, EXIT_SUCCESS);
            break;
        case 'v':
            print_version();
            break;
        default:
            usage(g_progname, EXIT_SUCCESS);
            break;
        }
    }

    DEBUG_LOG(LOG_INFO, "%s program running .........", g_progname);
    DEBUG_LOG(LOG_DEBUG, "group addr:%s", grpaddr);
    DEBUG_LOG(LOG_DEBUG, "log file:%s.", logfile); 
    DEBUG_LOG(LOG_DEBUG, "inft0-%s, intf1-%s.", intf0, intf1); 

    //init signal process handler
    setup_signal_handler();

    // create intf0 multicast socket
    socketintf0 = create_multicast_socket_intf(port, grpaddr, intf0);
    if (socketintf0 < 0)
    {
        DEBUG_LOG(LOG_ERROR, "create intf0 multicast socket error.");
        goto error;
    }

    strcpy(intf0req.ifr_ifrn.ifrn_name, intf0);
    if (0 > (ret = ioctl(socketintf0, SIOCGIFADDR, &intf0req)))
    {
        DEBUG_LOG(LOG_FATAL, "ioctl get SIOCGIFADDR failed. ret = %d", ret);
        close(socketintf0);
        return ERROR;
    }
    intf0ip = (struct sockaddr_in *)&(intf0req.ifr_addr);
    DEBUG_LOG(LOG_DEBUG, "intf0ip:0x%x.", intf0ip->sin_addr.s_addr);

    // create intf1 multicast socket 
    socketintf1 = create_multicast_socket_intf(port, grpaddr, intf1);
    if (socketintf1 < 0)
    {
        DEBUG_LOG(LOG_ERROR, "create intf1 multicast socket error.");
        goto error;
    }

    strcpy(intf1req.ifr_ifrn.ifrn_name, intf1);
    if (0 > (ret = ioctl(socketintf1, SIOCGIFADDR, &intf1req)))
    {
        DEBUG_LOG(LOG_FATAL, "ioctl get SIOCGIFADDR failed. ret = %d", ret);
        close(socketintf1);
        return ERROR;
    }
    intf1ip = (struct sockaddr_in *)&(intf1req.ifr_addr);
    DEBUG_LOG(LOG_DEBUG, "intf1ip:0x%x.", intf1ip->sin_addr.s_addr);

    intf0srcaddr.sin_family = AF_INET;
    intf1srcaddr.sin_family = AF_INET;

    intf0dstaddr.sin_family = AF_INET;                                                                                                                                                                         
    intf0dstaddr.sin_addr.s_addr = inet_addr(grpaddr);
    intf0dstaddr.sin_port = htons(port);

    intf1dstaddr.sin_family = AF_INET;                                                                                                                                                                         
    intf1dstaddr.sin_addr.s_addr = inet_addr(grpaddr);
    intf1dstaddr.sin_port = htons(port);

    while (true) 
    {
        FD_ZERO(&selset);
        FD_SET(socketintf0, &selset);
        FD_SET(socketintf1, &selset);

        maxsock = socketintf0 > socketintf1? socketintf0 : socketintf1;
        retval = select(maxsock + 1, &selset, NULL, NULL, NULL);
        if (retval < 0)
        {
            DEBUG_LOG(LOG_ERROR, "select error exit.");
            goto error;
        }
        
        if (retval == 0)
        {
            DEBUG_LOG(LOG_WARN, "select continue.");
            continue;
        }

        if (FD_ISSET(socketintf0, &selset))
        {
            len = recvfrom(socketintf0, pbuf, MAX_MSG_LENGTH, 0,  \
                    (struct sockaddr *)&intf0srcaddr, &socklen);
            if (len < 0)
            {
                DEBUG_LOG(LOG_ERROR, "socketintf0 recv failed, len = %d.", len);
                continue;
            }

            intf0_recv_count++;
            DEBUG_LOG(LOG_DEBUG, "intf0 src addr:0x%x, intf0 ip addr:0x%x.", \
                        intf0srcaddr.sin_addr.s_addr, intf0ip->sin_addr.s_addr);
            if (intf0srcaddr.sin_addr.s_addr != intf0ip->sin_addr.s_addr)
            {
                ret = sendto(socketintf1, (char  *)pbuf, len, 0,  \
                            (struct sockaddr *)&intf1dstaddr, sizeof(struct sockaddr));
                if (ret < 0) {
                    DEBUG_LOG(LOG_ERROR, "sendto intf0 ret = %d, error:%s\n", \
                                        ret, strerror(errno));
                }
                else
                {
                    intf0_send_count++;
                }
            }
            memset(pbuf, 0, MAX_MSG_LENGTH);
            DEBUG_LOG(LOG_DEBUG, "intf0 receive message len = %d, recv msg = %d, send msg = %d", 
                                 len, intf0_recv_count, intf0_send_count);
        }

        if (FD_ISSET(socketintf1, &selset))
        {
            len = recvfrom(socketintf1, pbuf, MAX_MSG_LENGTH, 0,  \
                    (struct sockaddr *)&intf1srcaddr, &socklen);
            if (len < 0)
            {
                DEBUG_LOG(LOG_ERROR, "socketintf1 recv failed, len = %d.", len);
                continue;
            }

            intf1_recv_count++;

            DEBUG_LOG(LOG_DEBUG, "intf1 src addr:0x%x, intf1 ip addr:0x%x.", \
                        intf1srcaddr.sin_addr.s_addr, intf1ip->sin_addr.s_addr);
            if (intf1srcaddr.sin_addr.s_addr != intf1ip->sin_addr.s_addr)
            {
                ret = sendto(socketintf0, (char  *)pbuf, len, 0,  \
                        (struct sockaddr *)&intf0dstaddr, sizeof(struct sockaddr));
                if (ret < 0) 
                {
                    DEBUG_LOG(LOG_ERROR, "sendto intf0 ret = %d, error:%s\n", \
                                        ret, strerror(errno));
                }
                else
                {
                    intf1_send_count++;
                }
            }

            memset(pbuf, 0, MAX_MSG_LENGTH);
            DEBUG_LOG(LOG_DEBUG, "intf1 receive message len = %d, recv msg = %d,"
                                 " send msg = %d", len, intf1_recv_count, intf1_send_count);
        }
    }

error: 
    close(socketintf0); 
    close(socketintf1);
    free(pbuf); 
    template_exit();

    return 0;
}
